﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;

namespace Borg.Framework
{
    public class CompositionRestorer : CompositionPersistor, IDisposable
    {
        private readonly bool _ignoreExisting;
        private readonly StreamReader _reader;
        private readonly IList<SlotInfo> _slotsToRestore;

        private CompositionRestorer(string filename, bool ignoreExisting)
        {
            _reader = new StreamReader(new IsolatedStorageFileStream(filename, FileMode.Open, store));
            _slotsToRestore = new List<SlotInfo>();
            _ignoreExisting = ignoreExisting;
        }

        public void Dispose()
        {
            _reader.Dispose();
        }

        private static PluginInfo GetPluginInfo(ExtensionRestoreInfo info)
        {
            return Runtime.Repository.PluginInfos.
                Find(info.PluginName).
                FirstOrDefault(pi => pi.RepositoryId.Uri.
                    Equals(info.Uri));
        }

        private static PluginInfo GetPluginInfo(string pluginName, Uri uri)
        {
            return Runtime.Repository.PluginInfos.
                Find(pluginName).
                FirstOrDefault(pi => pi.RepositoryId.Uri.
                    Equals(uri));
        }

        private static List<SavedCompilationInfo> GetRootExtensionsOfFile(string filename)
        {
            var list = new List<SavedCompilationInfo>();
            var reader = new StreamReader(new IsolatedStorageFileStream(filename, FileMode.Open, store));

            reader.ReadLine();
            var description = reader.ReadLine();

            var entry = reader.ReadLine().Split(new[] { "SLOT " }, StringSplitOptions.RemoveEmptyEntries);
            var info = entry[0].Split(new[] { ';' });
            
            var rootSlots = new List<SlotInfo>();
            var pluginInfo = GetPluginInfo(info[3], new Uri(info[4]));

            if (pluginInfo != null)
            {
                var eti = pluginInfo.ExtensionTypeInfos[info[2]];

                if (eti.ExtensionInfos.Count != 0)
                {
                    int i;

                    for (i = 1; i < entry.Length; i++)
                    {
                        var split = entry[i].Split(new[] { ';' });
                        var slot = split[split.Length - 1];

                        rootSlots.Add(eti.ExtensionInfos[0].SlotInfos[slot]);
                    }

                    for (i = 0; i < eti.ExtensionInfos.Count; i++)
                        list.Add(new SavedCompilationInfo(filename, description, eti.ExtensionInfos[i], rootSlots));
                }
                else
                {
                    list.Add(new SavedCompilationInfo(filename, description, null, null));
                }
            }
            else
            {
                list.Add(new SavedCompilationInfo(filename, description, null, null));
            }
            reader.Dispose();
            return list;
        }

        public static List<SavedCompilationInfo> GetSavedCompilationInfo(string filename)
        {
            return GetRootExtensionsOfFile(filename);
        }

        public static List<SavedCompilationInfo> GetSavedCompilations()
        {
            var list = new List<string>();
            list.AddRange(store.GetFileNames());

            var info = new List<SavedCompilationInfo>();

            foreach (var item in list)
                info.AddRange(GetRootExtensionsOfFile(item));

            return info;
        }

        private void Restore(ExtensionInfo root)
        {
            try
            {
                if (_reader.ReadLine() != "Borg Compilation Version 0.1.3.0")
                    throw new Exception("Invalid file version");

                _reader.ReadLine();

                Runtime.AutoPlug = false;
                Runtime.AutoRegister = false;
                Runtime.AutoOpen = false;
                
                composer = Runtime.DisableComposition();
                SlotInfo slot = null;

                while (!_reader.EndOfStream)
                {
                    RestoreLine(_reader.ReadLine(), slot, root);

                    if (_slotsToRestore.Count > 0)
                    {
                        slot = _slotsToRestore[0];
                        _slotsToRestore.RemoveAt(0);
                    }
                    else
                        slot = null;
                }
            }
            finally
            {
                try
                {
                    if (composer != null)
                        composer.EnableComposition();
                }
                finally
                {
                    if (_reader != null)
                        _reader.Close();
                }

                Runtime.AutoPlug = true;
                Runtime.AutoRegister = true;
                Runtime.AutoOpen = true;
            }
        }

        public static void Restore(string filename, ExtensionInfo root)
        {
            Restore(filename, root, false);
        }

        public static void Restore(string filename, ExtensionInfo root, bool ignoreExisting)
        {
            using (var restorer = new CompositionRestorer(filename, ignoreExisting))
                restorer.Restore(root);
        }

        private void RestoreExtension(string extensionAsString, SlotInfo slot, ExtensionInfo root)
        {
            var src = extensionAsString.Split(new[] { "SLOT " }, StringSplitOptions.RemoveEmptyEntries);
            var dst = new string[src.Length - 1];
            
            Array.Copy(src, 1, dst, 0, dst.Length);

            var info = ExtensionRestoreInfo.Parse(src[0]);
            var pluginInfo = GetPluginInfo(info);

            if (pluginInfo == null)
                throw new Exception(string.Format("Plugin {0} not found", info.PluginName));

            var eti = pluginInfo.ExtensionTypeInfos[info.ExtensionTypeName];
            
            if (info.IsRoot)
            {
                if (eti.ExtensionInfos.Count == 0)
                    throw new Exception(
                        string.Format(
                            "Restoration root extension {0} not found in composition", eti.Name));

                RestoreSlots(root, dst);
            }
            else if (src[0].StartsWith("R"))
                composer.Register(slot, eti.PlugTypeInfos[slot.Name]);
            else if (src[0].StartsWith("P"))
            {
                ExtensionInfo sharedExtension;

                if (info.IsShared)
                    sharedExtension = composer.GetSharedExtension(eti, true);
                else if (eti.ExtensionInfos.Count != 0)
                    sharedExtension =
                        _ignoreExisting
                            ? eti.ExtensionInfos[0]
                            : composer.CreateUniqueExtension(eti);
                else
                    sharedExtension = composer.CreateUniqueExtension(eti);

                composer.Plug(slot, sharedExtension.PlugInfos[slot.Name]);

                if (dst.Length > 0)
                    RestoreSlots(sharedExtension, dst);
            }
            else if (src[0].StartsWith("S"))
            {
                var ei = eti.ExtensionInfos[eti.ExtensionInfos.Count - 1];
                composer.SelectPlugs(slot, new[] {ei.PlugInfos[slot.Name]});
            }
        }

        private void RestoreLine(string extensionsAsString, SlotInfo slot, ExtensionInfo root)
        {
            var ext = extensionsAsString.Split(
                new[] { "EXT " }, StringSplitOptions.RemoveEmptyEntries);

            foreach (var item in ext)
                RestoreExtension(item, slot, root);
        }

        private void RestoreSlot(SlotInfo slot, SlotRestoreInfo info)
        {
            composer.SetAutoOpen(slot, info.AutoOpen);
            composer.SetAutoPlug(slot, info.AutoPlug);
            composer.SetAutoRegister(slot, info.AutoRegister);
            composer.SetAutoRelease(slot, info.AutoRelease);

            composer.SetLazyLoad(slot, info.LazyLoad);
            composer.SetUnique(slot, info.Unique);
            composer.SetSelectionMode(slot, info.selectionMode);

            if (!slot.IsOpen)
                composer.Open(slot);

            _slotsToRestore.Add(slot);
        }

        private void RestoreSlots(ExtensionInfo ext, IEnumerable<string> slotsAsString)
        {
            foreach (var sri in slotsAsString.Select(SlotRestoreInfo.Parse))
                RestoreSlot(ext.SlotInfos[sri.Name], sri);
        }

        private class ExtensionRestoreInfo
        {
            public string ExtensionTypeName;
            public bool IsRoot;
            public bool IsShared;
            public string PluginName;
            public Uri Uri;

            private ExtensionRestoreInfo()
            { }

            private static string FormatDateTime(DateTime? dateTime)
            {
                return dateTime.ToString();
            }

            public static ExtensionRestoreInfo Parse(string s)
            {
                var info = new ExtensionRestoreInfo
                {
                    IsRoot = s.StartsWith("ROOT")
                };

                var split = s.Substring(s.IndexOf(';') + 1).Split(new[] { ';' });

                info.IsShared = split[0] == "S";
                info.ExtensionTypeName = split[1];
                info.PluginName = split[2];
                info.Uri = new Uri(split[3]);
                info.LastWriteTime = DateTime.Parse(split[4]);
                
                return info;
            }

            private static DateTime? ParseDateTime(string dateTime)
            {
                string[] split = dateTime.Split(new[] { ' ' });
                string[] date = split[0].Split(new[] { '.' });
                string[] time = split[1].Split(new[] { ':' });

                var day = Convert.ToInt32(date[0]);
                var month = Convert.ToInt32(date[1]);
                var year = Convert.ToInt32(date[2]);
                
                var hour = Convert.ToInt32(time[0]);
                var minute = Convert.ToInt32(time[1]);

                return new DateTime(year, month, day, hour, minute, Convert.ToInt32(time[2]));
            }

            public DateTime? LastWriteTime { get; set; }
        }

        public class SavedCompilationInfo : IComparable
        {
            private readonly string _description;
            private readonly ExtensionInfo _root;
            private readonly List<SlotInfo> _rootSlots;
            private readonly string _storedFile;

            public SavedCompilationInfo(string filename, string description,
                ExtensionInfo root, List<SlotInfo> rootSlots)
            {
                _storedFile = filename;
                _description = description;
                _root = root;
                _rootSlots = rootSlots;
            }

            public int CompareTo(object obj)
            {
                var info = (SavedCompilationInfo)obj;
                return String.Compare(Description, info.Description, StringComparison.Ordinal);
            }

            public string Description
            {
                get { return _description; }
            }

            public ExtensionInfo Root
            {
                get { return _root; }
            }

            public List<SlotInfo> RootSlots
            {
                get { return _rootSlots; }
            }

            public string StoredFile
            {
                get { return _storedFile; }
            }
        }

        private class SlotRestoreInfo
        {
            public bool AutoOpen;
            public bool AutoPlug;
            public bool AutoRegister;
            public bool AutoRelease;
            public bool LazyLoad;
            public string Name;
            public SelectionMode selectionMode;
            public bool Unique;

            private SlotRestoreInfo()
            { }

            public static SlotRestoreInfo Parse(string s)
            {
                var split = s.Split(new[] { ';' });

                return new SlotRestoreInfo
                           {
                               AutoOpen = split[0].EndsWith("=t"),
                               AutoPlug = split[1].EndsWith("=t"),
                               AutoRegister = split[2].EndsWith("=t"),
                               AutoRelease = split[3].EndsWith("=t"),
                               LazyLoad = split[4].EndsWith("=t"),
                               Unique = split[5].EndsWith("=t"),
                               selectionMode = split[6].EndsWith("Single")
                                                   ? SelectionMode.Single
                                                   : SelectionMode.Multiple,
                               Name = split[7]
                           };
            }
        }
    }
}

